﻿using anydata.Entitys;
using Microsoft.AspNetCore.Mvc;

using MongoDB.Bson;
using MongoDB.Driver;

using Newtonsoft.Json.Linq;

using System.Text.RegularExpressions;

namespace anydata.Extensions
{
    public static class GlobalExtrensions
    {
        public static string FullMessage(this Exception ex)
        {
            if (ex.InnerException != null)
            {
                return ex.InnerException.FullMessage();
            }
            return ex.Message;
        }

        public static async Task<IActionResult> TryExecuteResult(this Task<OperateResult> operate)
        {
            return (await operate.TryExecute()).Result();
        }

        public static async Task<OperateResult> TryExecute(this Task<OperateResult> operate)
        {
            try
            {
                var cancel = Task.Delay(Config.ExecutorTimeout);
                await Task.WhenAny(operate, cancel);
                if (!cancel.IsCompleted)
                {
                    return operate.Result;
                }
                return OperateResult.Faild("超时取消");
            }
            catch (Exception ex)
            {
                return OperateResult.Faild(ex.Message);
            }
        }

        public static async Task TryExecute(this Task operate)
        {
            try
            {
                await operate;
            }
            catch { }
        }

        public static EntityBase Update(this EntityBase local, EntityBase update)
        {
            local.isDeleted = update.isDeleted == true;
            foreach (var (k, p) in update.extensions)
            {
                local.extensions[k] = p;
            }
            local.labels = local.labels ?? new List<string>();
            foreach (var label in update.labels)
            {
                if (!string.IsNullOrWhiteSpace(label) && !local.labels.Contains(label))
                {
                    local.labels.Add(label);
                }
            }
            return local;
        }

        public static async Task<object> NewRootAsync(this Task<List<object>> replaceRoot)
        {
            var retList = new List<JToken?>();
            var temp = (await replaceRoot);
            if (temp != default)
            {
                retList = temp?.Select(i => i.NewRootData()).Where(i => i != default).ToList();
            }
            if (retList?.Count > 1)
            {
                return retList;
            }
            return retList?.FirstOrDefault() ?? new object();
        }

        public static JToken? NewRootData(this object data)
        {
            if (data != default)
            {
                var newRoot = JToken.FromObject(data).SelectToken("newRoot");
                if (newRoot != default && newRoot.SelectToken("_id") != default)
                {
                    newRoot["id"] = newRoot["_id"];
                    newRoot.SelectToken("_id")?.Parent?.Remove();
                }
                return newRoot;
            }
            return default;
        }

        public static List<string> Names(JToken? data)
        {
            if (data != default)
            {
                switch (data.Type)
                {
                    case JTokenType.Object:
                        return ((JObject)data).Properties().Select(i => i.Name).ToList();
                    case JTokenType.Array:
                        return ((JArray)data).SelectMany(i => Names(i)).Distinct().ToList();
                }
            }
            return new List<string>();
        }

        public static BsonDocument ToBDocument(this JToken data, bool query = false)
        {
            switch (data.Type)
            {
                case JTokenType.Object:
                    return data.ToBson().Analysis(query).AsBsonDocument;
                default:
                    throw new BsonSerializationException("必须是对象类型!");
            }
        }

        public static bool IsDateTime(this string strSource, out DateTime date)
        {
            date = DateTime.MinValue;
            if (!string.IsNullOrWhiteSpace(strSource) && strSource.Length >= 8)
            {
                if (DateTime.TryParse(strSource, out date) && date > new DateTime(1600, 1, 1))
                {
                    return true;
                }
            }
            return false;
        }

        public static BsonValue Analysis(this BsonValue bsonValue, bool query = false)
        {
            switch (bsonValue.BsonType)
            {
                case BsonType.String:
                    var temp = bsonValue.AsString.Trim();
                    switch (temp)
                    {
                        case "sysdate()":
                            return new BsonDateTime(DateTime.Now);
                        case "snowId()":
                            return new BsonString(SnowflakeIDcreator.nextId().ToString());
                    }
                    if (temp.Length == 24 && ObjectId.TryParse(temp, out var id))
                    {
                        return id;
                    }
                    if (temp.StartsWith("function", StringComparison.OrdinalIgnoreCase))
                    {
                        return new BsonJavaScript(temp);
                    }
                    if (temp.IsDateTime(out var date))
                    {
                        return new BsonDateTime(date);
                    }
                    break;
                case BsonType.Array:
                    var bsonArray = new BsonArray();
                    foreach (var item in bsonValue.AsBsonArray)
                    {
                        bsonArray.Add(item.Analysis(query));
                    }
                    return bsonArray;
                case BsonType.Document:
                    var bson = new BsonDocument();
                    foreach (var item in bsonValue.AsBsonDocument)
                    {
                        if (item.Name.StartsWith("_") && item.Name.EndsWith("_"))
                        {
                            bson.Add("$" + item.Name.Substring(1, item.Name.Length - 2), item.Value.Analysis(query));
                        }
                        else if (query && item.Name == "id")
                        {
                            bson.Add("_id", item.Value.Analysis(query));
                        }
                        else
                        {
                            bson.Add(item.Name, item.Value.Analysis(query));
                        }
                    }
                    return bson;
            }
            return bsonValue;
        }

        public static IAggregateFluent<TDocument> Aggregate<TDocument>(this IMongoCollection<TDocument> collection, JToken query, bool check = true)
        {
            var limit = false;
            var options = new AggregateOptions() { AllowDiskUse = true };
            var aggregate = collection.Aggregate(options);
            if (query != null)
            {
                aggregate = aggregate.Aggregate(query, ref limit);
            }
            if (check)
            {
                if (!limit)
                {
                    return aggregate.Count().As<TDocument>();
                }
            }
            return aggregate;
        }

        public static IAggregateFluent<TDocument> Aggregate<TDocument>(this IAggregateFluent<TDocument> aggregate, JToken query, ref bool limit)
        {
            switch (query.Type)
            {
                case JTokenType.Object:
                    foreach (JProperty item in query)
                    {
                        aggregate = aggregate.Operation(item.Name, item.Value);
                        if (item.Name.ToLower() == "limit")
                        {
                            limit = true;
                        }
                    }
                    break;
                case JTokenType.Array:
                    foreach (var subquery in query)
                    {
                        aggregate = aggregate.Aggregate(subquery, ref limit);
                    }
                    break;
            }
            return aggregate;
        }
        public static IAggregateFluent<TDocument> Operation<TDocument>(this IAggregateFluent<TDocument> aggregate, string operation, JToken value)
        {
            switch (operation.ToLower())
            {
                case "match":
                    aggregate = aggregate.Match(value.ToBDocument(true));
                    break;
                case "project":
                    aggregate = aggregate.Project<TDocument>(value.ToBDocument(true));
                    break;
                case "group":
                    var doc = value.ToBDocument(true);
                    var keyToken = doc.GetElement("key").Value;
                    doc.Remove("key");
                    switch (keyToken.BsonType)
                    {
                        case BsonType.String:
                            var key = keyToken.ToString();
                            doc.Add("_id", $"${key}");
                            doc.Add(key, new BsonDocument("$first", $"${key}"));
                            break;
                        case BsonType.Array:
                            var _id = new BsonDocument();
                            foreach (string item in keyToken.AsBsonArray)
                            {
                                _id.Add(item, $"${item}");
                                doc.Add(item, new BsonDocument("$first", $"${item}"));
                            }
                            doc.Add("_id", _id);
                            break;
                        case BsonType.Document:
                            doc.Add("_id", keyToken.AsBsonDocument);
                            break;
                    }
                    aggregate = aggregate.Group(doc).As<TDocument>();
                    break;
                case "sort":
                    aggregate = aggregate.Sort(value.ToBDocument(true));
                    break;
                case "skip":
                    aggregate = aggregate.Skip(value.ToObject<int>());
                    break;
                case "limit":
                    var limit = value.ToObject<int>();
                    aggregate = aggregate.Limit(limit < 10240 ? limit : 10240);
                    break;
                case "lookup":
                    var localField = value.Value<string>("localField");
                    var foreignField = value.Value<string>("foreignField");
                    if (localField == "id")
                    {
                        localField = "_id";
                    }
                    if (foreignField == "id")
                    {
                        foreignField = "_id";
                    }
                    aggregate = aggregate.Lookup(value.Value<string>("from"),
                        localField, foreignField,
                        value.Value<string>("as")).As<TDocument>();
                    break;
                case "replaceroot":
                    aggregate = aggregate.ReplaceRoot<TDocument>(value.ToBDocument(true));
                    break;
                case "replacewith":
                    aggregate = aggregate.ReplaceWith<TDocument>(value.ToBDocument(true));
                    break;
                case "unwind":
                    aggregate = aggregate.Unwind<TDocument>(value.ToObject<string>());
                    break;
            }
            return aggregate;
        }
    }
}
